use crate::{attr::AttrConstant, reader::Stream};

#[derive(Debug)]
pub enum Constant {
	Empty,
	Utf8(String),
	Interger(i32),
	Float(f32),
	Long(i64),
	Double(f64),
	Class(u16),           // name,index
	String(u16),          // utf8 index
	FieldRef(u16, u16),   // class index, name_type index,
	MethodRef(u16, u16),  // class index, name_type index,
	IMethodRef(u16, u16), // class index,name_type index,
	NameType(u16, u16),   // name index,desc index
	MethodHandle(u8, u16),
	MethodType(u16),
	InvokeDynamic(u16, u16),
}

#[rustfmt::skip]
impl Constant {
	const UTF8: u8            = 1;
	const INTERGER: u8        = 3;
	const FLOAT: u8           = 4;
	const LONG: u8            = 5;
	const DOUBLE: u8          = 6;
	const CLASS: u8           = 7;
	const STRING: u8          = 8;
	const FIELD_REF: u8       = 9;
	const METHOD_REF: u8      = 10;
	const I_METHOD_REF: u8    = 11;
	const NAME_TYPE: u8       = 12;
	const METHOD_HANDLE: u8   = 15;
	const METHOD_TYPE: u8     = 16;
	const INVOKE_DYNAMIC: u8  = 18;
}
impl Constant {
	pub fn to_constant(&self, constants: &Vec<Constant>) -> Option<AttrConstant> {
		match self {
			Self::Interger(v) => Some(AttrConstant::Interger(*v)),
			Self::Float(v) => Some(AttrConstant::Float(*v)),
			Self::Long(v) => Some(AttrConstant::Long(*v)),
			Self::Double(v) => Some(AttrConstant::Double(*v)),
			Self::String(v) => constants.get(*v as usize)?.to_constant(constants),
			Self::Utf8(v) => Some(AttrConstant::String(v.clone())),
			_ => None,
		}
	}
	pub fn to_string(&self, constants: &Vec<Constant>) -> Option<String> {
		match self {
			Self::Utf8(v) => Some(v.clone()),
			Self::String(idx) => constants.get(*idx as usize)?.to_string(constants),
			Self::Class(idx) => constants.get(*idx as usize)?.to_string(constants),
			_ => None,
		}
	}
	pub fn to_name_type(&self, constants: &Vec<Constant>) -> Option<(String, String)> {
		match self {
			Self::NameType(idx_name, idx_sign) => Some((
				constants.get(*idx_name as usize)?.to_string(constants)?,
				constants.get(*idx_sign as usize)?.to_string(constants)?,
			)),
			_ => None,
		}
	}
	pub fn to_ref_name_type(&self, constants: &Vec<Constant>) -> Option<(String, String, String)> {
		match self {
			Self::FieldRef(class_idx, name_type_idx) => {
				let (name, sign) = constants.get(*name_type_idx as usize)?.to_name_type(constants)?;
				Some((constants.get(*class_idx as usize)?.to_string(constants)?, name, sign))
			}
			Self::MethodRef(class_idx, name_type_idx) => {
				let (name, sign) = constants.get(*name_type_idx as usize)?.to_name_type(constants)?;
				Some((constants.get(*class_idx as usize)?.to_string(constants)?, name, sign))
			}
			Self::IMethodRef(class_idx, name_type_idx) => {
				let (name, sign) = constants.get(*name_type_idx as usize)?.to_name_type(constants)?;
				Some((constants.get(*class_idx as usize)?.to_string(constants)?, name, sign))
			}
			_ => None,
		}
	}
	pub fn parse(s: &mut Stream) -> Option<Self> {
		let tag: u8 = s.read()?;
		Some(match tag {
			Self::UTF8 => {
				let len: u16 = s.read()?;
				let bytes = s.read_bytes(len as _)?;
				let string = cesu8::from_java_cesu8(bytes).ok()?;
				Constant::Utf8(string.to_string())
			}
			Self::INTERGER => Constant::Interger(s.read()?),
			Self::FLOAT => Constant::Float(s.read()?),
			Self::LONG => Constant::Long(s.read()?),
			Self::DOUBLE => Constant::Double(s.read()?),
			Self::CLASS => Constant::Class(s.read()?),
			Self::STRING => Constant::String(s.read()?),
			Self::FIELD_REF => Constant::FieldRef(s.read()?, s.read()?),
			Self::METHOD_REF => Constant::MethodRef(s.read()?, s.read()?),
			Self::I_METHOD_REF => Constant::IMethodRef(s.read()?, s.read()?),
			Self::NAME_TYPE => Constant::NameType(s.read()?, s.read()?),
			Self::METHOD_HANDLE => Constant::MethodHandle(s.read()?, s.read()?),
			Self::METHOD_TYPE => Constant::MethodType(s.read()?),
			Self::INVOKE_DYNAMIC => Constant::InvokeDynamic(s.read()?, s.read()?),
			// _ => return None,
			_ => panic!("java.lang.ClassFormatError: constant pool tag!"),
		})
	}
}
